/* 
Scenic Graphics Library
Copyright (C) 2007 Jouni Tulkki

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USApackage scenic;
*/

package scenic;

import java.awt.Rectangle;
import java.awt.geom.AffineTransform;

/**
 * This class contains utility methods.
 */
public class Util {
	/**
	 * Combines the given rectangle with the given point. The given 
	 * rectangle is modified so that it includes the given point.
	 * 
	 * @param a the rectangle.
	 * @param x the x coordinate of the point
	 * @param y the y coodrinate of the point
	 */
	public static void combine(Rectangle a, int x, int y) {
		if(a.width == 0) {
			a.x = x;
			a.y = y;
			a.width = 1;
			a.height = 1;
			return;
		}
		int oldx = a.x;
		int oldy = a.y;

		a.x = Math.min(a.x, x);
		a.y = Math.min(a.y, y);
		a.width = Math.max(oldx + a.width, x + 1) - a.x;
		a.height = Math.max(oldy + a.height, y + 1) - a.y;
	}
	
	/**
	 * Combines the two rectangles. The rectangle a is modified
	 * so that it contains the are defined by rectangle b.
	 * 
	 * @param a the first rectangle.
	 * @param b the second rectangle.
	 */
	public static void combine(Rectangle a, Rectangle b) {
		if(a.width == 0) {
			a.x = b.x;
			a.y = b.y;
			a.width = b.width;
			a.height = b.height;
			return;
		}
		int oldx = a.x;
		int oldy = a.y;

		a.x = Math.min(a.x, b.x);
		a.y = Math.min(a.y, b.y);
		a.width = Math.max(oldx + a.width, b.x + b.width) - a.x;
		a.height = Math.max(oldy + a.height, b.y + b.height) - a.y;
	}
	
	/**
	 * Helper method which modifies the rectangle so that it contains
	 * the given point when using the given transform.
	 * 
	 * @param tm the transform.
	 * @param r the rectangle.
	 * @param xp the x coordinate of the point.
	 * @param yp the y coordinate of the point.
	 */
	public static void addPointToBounds(AffineTransform tm, Rectangle r, 
			double xp, double yp) {
		int xs = (int)Math.floor(tm.getScaleX() * xp + tm.getShearX() * yp + tm.getTranslateX());
		int ys = (int)Math.floor(tm.getShearY() * xp + tm.getScaleY() * yp + tm.getTranslateY());
		
		Util.combine(r, xs, ys);
	}
	
	/**
	 * Calculates the common area shared by the rectangles. 
	 * The rectangle a is modified so that it contains the 
	 * common area of the two rectangles. The common area
	 * is the largest rectangle that is inside the two
	 * rectangles.
	 * 
	 * @param a the first rectangle.
	 * @param b the second rectangle.
	 */
	public static void common(Rectangle a, Rectangle b) {
		int oldx = a.x;
		int oldy = a.y;

		a.x = Math.max(a.x, b.x);
		a.y = Math.max(a.y, b.y);
		a.width = Math.min(oldx + a.width, b.x + b.width) - a.x;
		a.height = Math.min(oldy + a.height, b.y + b.height) - a.y;
	}
	
	public static boolean hasCommonArea(Rectangle a, Rectangle b) {
		return b.x < a.x + a.width && b.y < a.y + a.height &&
			a.x < b.x + b.width && a.y < b.y + b.height;
	}
	
	/**
	 * Returns a power of two that is larger or equal to the given value.
	 * Power of two is a value of the form 2^n, where n is any integer >= 0.
	 * So, for example, for the value 30 this method would return 32.
	 * 
	 * @param size the value.
	 * @return the power of two.
	 */
	public static int toPowerOfTwo(int size) {
		int s = 1;
		
		if(size == 0)
			return 0;
		while(s < size)
			s *= 2;
		return s;
	}	
	
	/**
	 * Returns a rectangle that contains the area of the given rectangle transformed
	 * by the given transform.
	 * 
	 * @param a the transform.
	 * @param b the rectangle.
	 * @return the combination.
	 */
	public static Rectangle transform(AffineTransform a, Rectangle b) {
		Rectangle r = new Rectangle();
		double m00 = a.getScaleX();
		double m01 = a.getShearX();
		double m10 = a.getShearY();
		double m11 = a.getScaleY();
		double dx = a.getTranslateX();
		double dy = a.getTranslateY();

		r.x = (int)Math.floor(m00 * ((m00 > 0) ? b.x : (b.x + b.width)) + 
				m01 * ((m01 > 0) ? b.y : (b.y + b.height)) +
				dx); 
		r.y = (int)Math.floor(m10 * ((m10 > 0) ? b.x : (b.x + b.width)) +
			m11 * ((m11 > 0) ? b.y : (b.y + b.height)) +
			dy); 
		r.width = (int)Math.ceil(m00 * ((m00 < 0) ? b.x : (b.x + b.width)) + 
				m01 * ((m01 < 0) ? b.y : (b.y + b.height)) +
				dx) - r.x; 
		r.height = (int)Math.ceil(m10 * ((m10 < 0) ? b.x : (b.x + b.width)) +
			m11 * ((m11 < 0) ? b.y : (b.y + b.height)) +
			dy) - r.y; 
			
		return r;
	}
	
	public static AffineTransform multiply(AffineTransform a, AffineTransform b) {
		AffineTransform tm = (AffineTransform)a.clone();
		
		tm.concatenate(b);
		
		return tm;
	}
	
	public static AffineTransform linearTransform(AffineTransform a) {
		return new AffineTransform(
				a.getScaleX(), 
				a.getShearY(),
				a.getShearX(),
				a.getScaleY(),
				0.0, 
				0.0);
	}
}
